﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using Microsoft.Xna.Framework.Graphics;

namespace Tomahawk.Runtime.Rendering.Materials
{
    public sealed class SASScript
    {
        #region Script Context
        public struct Context
        {
            public SASEffectEnvironment sasEnvironment;
            public SASMaterial sasEffect;
            public SASTechnique sasTechnique;
            public SASPass sasPass;

            public PrimitiveType primitiveType;
            public int baseVertex;
            public int numVertices;
            public int startIndex;
            public int primitiveCount;

            public GraphicsDevice device;

            public Context(
                SASEffectEnvironment sasEnvironment, 
                SASMaterial sasEffect, 
                SASTechnique sasTechnique, 
                SASPass sasPass, 
                PrimitiveType primitiveType,
                int baseVertex,
                int minVertexIndex,
                int numVertices,
                int startIndex,
                int primitiveCount,
                GraphicsDevice device)
            {
                this.sasEnvironment = sasEnvironment;
                this.sasEffect = sasEffect;
                this.sasTechnique = sasTechnique;
                this.sasPass = sasPass;
                this.primitiveType = primitiveType;
                this.baseVertex = baseVertex;
                this.numVertices = numVertices;
                this.startIndex = startIndex;
                this.primitiveCount = primitiveCount;

                this.device = device;
            }

            public Context(
                SASEffectEnvironment sasEnvironment,
                SASMaterial sasEffect,
                GraphicsDevice device)
            {
                this.sasEnvironment = sasEnvironment;
                this.sasEffect = sasEffect;
                this.sasTechnique = null;
                this.sasPass = null;
                this.primitiveType = 0;
                this.baseVertex = 0;
                this.numVertices = 0;
                this.startIndex = 0;
                this.primitiveCount = 0;

                this.device = device;
            }

            public Context(
                SASEffectEnvironment sasEnvironment,
                SASMaterial sasEffect,
                SASTechnique sasTechnique,
                SASPass sasPass,
                ModelMesh mesh,
                ModelMeshPart part,
                GraphicsDevice device)
            {
                this.sasEnvironment = sasEnvironment;
                this.sasEffect = sasEffect;
                this.sasTechnique = sasTechnique;
                this.sasPass = sasPass;
                this.primitiveType = PrimitiveType.TriangleList;
                this.baseVertex = part != null ? part.BaseVertex : 0;
                this.numVertices = part != null ? part.NumVertices : 0;
                this.startIndex = part != null ? part.StartIndex : 0;
                this.primitiveCount = part != null ? part.PrimitiveCount : 0;

                this.device = device;
            }
        }
        #endregion

        #region Commands
        #region SASScriptCommand
        public abstract class SASScriptCommand
        {
            protected string value;
            public SASScriptCommand(string value)
            {
                this.value = value.ToUpper();
            }
            public abstract bool Execute(ref Context context);
        }
        #endregion

        #region SASScriptGroupCommand  
        public class SASScriptGroupCommand : SASScriptCommand
        {
            public List<SASScriptCommand> commands = new List<SASScriptCommand>();

            public SASScriptGroupCommand(string value)
                : base(value)
            {
            }

            public override bool Execute(ref Context context)
            {
                bool result = true;

                foreach (SASScriptCommand command in commands)
                {
                    #if DEBUG_SAS_SCRIPTS
                    Console.WriteLine(command.ToString());
                    #endif

                    result &= command.Execute(ref context);
                }
                return true;
            }
        }
        #endregion

        #region SASScriptLoopCommand
        public class SASScriptLoopCommand : SASScriptGroupCommand
        {
            public SASScriptLoopCommand(string value)
                : base(value)
            {
            }

            public override bool Execute(ref Context context)
            {
                if (context.sasEffect == null)
                {
                    return false;
                }

                SASParameter parameter = context.sasEffect.Parameters[value];

                bool result = true;
                int steps = 0;
                switch (parameter.Type)
                {
                    case SASParameter.SASParameterType.SASBool: steps = parameter.BoolValue ? 1 : 0; break;
                    case SASParameter.SASParameterType.SASInt: steps = parameter.IntValue; break;
                    case SASParameter.SASParameterType.SASFloat: steps = (int) Math.Round(parameter.FloatValue); break;
                }
                while (steps > 0)
                {
                    result = base.Execute(ref context);
                    --steps;
                }

                return result;
            }
        }
        #endregion

        #region SASScriptTechniqueCommand
        public class SASScriptTechniqueCommand : SASScriptCommand
        {
            public SASScriptTechniqueCommand(string value)
                : base(value)
            {
            }
            public override bool Execute(ref Context context)
            {
                if (context.sasEffect == null)
                {
                    return false;
                }
                
                return context.sasEffect.SelectTechniques(value);
            }
        }
        #endregion

        #region SASScriptPassCommand
        public class SASScriptPassCommand : SASScriptCommand
        {
            public SASScriptPassCommand(string value) : base(value)
            {
            }
            public override bool Execute(ref Context context)
            {
                if (context.sasEffect == null || context.sasTechnique == null)
                {
                    return false;
                }

                bool result = true;
                if (context.sasTechnique.PassTable.TryGetValue(value, out context.sasPass))
                {
                    result = context.sasPass.DrawMesh(ref context);
                }

                return result;
            }
        }
        #endregion

        #region SASScriptClearSetColorCommand
        public class SASScriptClearSetColorCommand : SASScriptCommand
        {
            public SASScriptClearSetColorCommand(string value)
                : base(value)
            {
            }
            public override bool Execute(ref Context context)
            {
                if (context.sasEffect == null)
                {
                    return false;
                }
                
                bool result = false;
                SASParameter sasParameter = context.sasEffect.Parameters[value];
                if (sasParameter != null)
                {
                    Engine.Instance.ClearColor = new Color(sasParameter.VectorValue);
                    result = true;
                }
                return result;
            }
        }
        #endregion

        #region SASScriptClearSetDepthCommand
        public class SASScriptClearSetDepthCommand : SASScriptCommand
        {
            public SASScriptClearSetDepthCommand(string value)
                : base(value)
            {
            }
            public override bool Execute(ref Context context)
            {
                if (context.sasEffect == null)
                {
                    return false;
                }
                
                bool result = false;
                SASParameter sasParameter = context.sasEffect.Parameters[value];
                if (sasParameter != null)
                {
                    Engine.Instance.ClearDepth = sasParameter.FloatValue;
                    result = true;
                }
                return result;
            }
        }
        #endregion

        #region SASScriptClearCommand
        public class SASScriptClearCommand : SASScriptCommand
        {
            public enum ClearMode
            {
                Color,
                Depth
            }

            protected ClearMode mode;

            public SASScriptClearCommand(string value)
                : base(value)
            {
                switch (this.value)
                {
                    case "COLOR": 
                    case "COLOR0": 
                    case "COLOR1": 
                    case "COLOR2": mode = ClearMode.Color; break;
                    case "DEPTH": mode = ClearMode.Depth; break;
                }
            }
            public override bool Execute(ref Context context)
            {
                if (context.device == null)
                {
                    return false;
                }

                bool result = true;

                ClearOptions options = 0;
                switch (mode)
                {
                    case ClearMode.Color: options |= ClearOptions.Target; break;
                    case ClearMode.Depth: options |= ClearOptions.DepthBuffer; break;
                }

                context.device.Clear(options, Engine.Instance.ClearColor, Engine.Instance.ClearDepth, 0);
                return result;
            }
        }
        #endregion

        #region SASScriptDepthStencilTargetCommand
        public class SASScriptDepthStencilTargetCommand : SASScriptCommand
        {
            public SASScriptDepthStencilTargetCommand(string value)
                : base(value)
            {
            }
            public override bool Execute(ref Context context)
            {
                if (context.device == null || context.sasEffect == null ||
                    !(context.sasEffect is SASScene))
                {
                    return false;
                }

                bool result = true;

                result = ((SASScene)context.sasEffect).ActivateDepthStencilBuffer(value, context.device);

                return result;
            }
        }
        #endregion

        #region SASScriptRenderColorTargetCommand
        public class SASScriptRenderColorTargetCommand : SASScriptCommand
        {
            protected int index = 0;

            public SASScriptRenderColorTargetCommand(int index, string value)
                : base(value)
            {
                this.index = index;
            }
            public override bool Execute(ref Context context)
            {
                if (context.device == null || context.sasEffect == null ||
                    !(context.sasEffect is SASScene))
                {
                    return false;
                }

                bool result = true;

                result = ((SASScene) context.sasEffect).ActivateRenderTarget(index, value, context.device);

                return result;
            }
        }
        #endregion

        #region SASScriptExternalCommand
        public class SASScriptExternalCommand : SASScriptCommand
        {
            public SASScriptExternalCommand(string value)
                : base(value)
            {
            }
            public override bool Execute(ref Context context)
            {
                if (context.device == null || context.sasEffect == null || !(context.sasEffect is SASScene))
                {
                    return false;
                }

                return context.sasEffect.DrawExternal(value, context.device);
            }
        }
        #endregion

        #region SASScriptDrawMeshCommand
        public class SASScriptDrawMeshCommand : SASScriptCommand
        {
            protected enum DrawMode
            {
                Geometry,
                Buffer
            }
            protected DrawMode drawMode;

            public SASScriptDrawMeshCommand(string value)
                : base(value)
            {
                switch (this.value)
                {
                    case "GEOMETRY": drawMode = DrawMode.Geometry; break;
                    case "BUFFER": drawMode = DrawMode.Buffer; break;
                }
            }

            public override bool Execute(ref Context context)
            {
                if (context.sasEffect == null || context.sasPass == null || context.device == null)
                {
                    return false;
                }

                bool result = true;
                SASMaterial effect = context.sasEffect;
                EffectPass pass = context.sasPass.Pass;
                GraphicsDevice device = context.device;

                pass.Begin();

                switch (drawMode)
                {
                    case DrawMode.Geometry:
                        {
                            device.DrawIndexedPrimitives(
                                context.primitiveType,
                                context.baseVertex,
                                0,
                                context.numVertices,
                                context.startIndex,
                                context.primitiveCount);
                        } break;
                    case DrawMode.Buffer:
                        {
                            effect.DrawQuad(device);
                        } break;
                }

                pass.End();

                return result;
            }
        }
        #endregion
        #endregion

        #region Properties
        private SASScriptGroupCommand main = new SASScriptGroupCommand("");
        #endregion

        public SASScript(string script)
        {
            Stack<SASScriptGroupCommand> commandStack = new Stack<SASScriptGroupCommand>();
            commandStack.Push(main);

            string[] lines = script.Split(new char[] { ';' });
            foreach (string line in lines)
            {
                string[] tokens = line.Split(new char[] { '=' });
                if (tokens.Count() == 2)
                {
                    string type = tokens[0].ToUpper();
                    string value = tokens[1];
                    switch (type)
                    {
                        case "CLEAR":
                            {
                                SASScriptClearCommand command = new SASScriptClearCommand(value);
                                commandStack.Peek().commands.Add(command);
                            } break;
                        case "CLEARSETCOLOR":
                            {
                                SASScriptClearSetColorCommand command = new SASScriptClearSetColorCommand(value);
                                commandStack.Peek().commands.Add(command);
                            } break;
                        case "CLEARSETDEPTH":
                            {
                                SASScriptClearSetDepthCommand command = new SASScriptClearSetDepthCommand(value);
                                commandStack.Peek().commands.Add(command);
                            } break;
                        case "RENDERDEPTHSTENCILTARGET":
                            {
                                SASScriptDepthStencilTargetCommand command = new SASScriptDepthStencilTargetCommand(value);
                                commandStack.Peek().commands.Add(command);
                            } break;
                        case "RENDERCOLORTARGET":
                        case "RENDERCOLORTARGET0":
                            {
                                SASScriptRenderColorTargetCommand command = new SASScriptRenderColorTargetCommand(0, value);
                                commandStack.Peek().commands.Add(command);
                            } break;
                        case "RENDERCOLORTARGET1":
                            {
                                SASScriptRenderColorTargetCommand command = new SASScriptRenderColorTargetCommand(1, value);
                                commandStack.Peek().commands.Add(command);
                            } break;
                        case "RENDERCOLORTARGET2":
                            {
                                SASScriptRenderColorTargetCommand command = new SASScriptRenderColorTargetCommand(2, value);
                                commandStack.Peek().commands.Add(command);
                            } break;
                        case "SCRIPTEXTERNAL":
                            {
                                SASScriptExternalCommand command = new SASScriptExternalCommand(value);
                                commandStack.Peek().commands.Add(command);
                            } break;
                        case "DRAW":
                            {
                                SASScriptDrawMeshCommand command = new SASScriptDrawMeshCommand(value);
                                commandStack.Peek().commands.Add(command);
                            } break;
                        case "TECHNIQUE":
                            {
                                SASScriptTechniqueCommand command = new SASScriptTechniqueCommand(value);
                                commandStack.Peek().commands.Add(command);
                            } break;
                        case "PASS":
                            {
                                SASScriptPassCommand command = new SASScriptPassCommand(value);
                                commandStack.Peek().commands.Add(command);
                            } break;
                        case "LOOPBYCOUNT":
                            {
                                SASScriptLoopCommand command = new SASScriptLoopCommand(value);
                                commandStack.Peek().commands.Add(command);
                                commandStack.Push(command);
                            } break;
                        case "LOOPEND":
                            {
                                commandStack.Pop();
                            } break;
                        //default: throw new NotImplementedException();
                    }
                }
            }
        }

        public bool Execute(ref Context context)
        {
            #if DEBUG_SAS_SCRIPTS
            Console.WriteLine("---------------------------------------");
            #endif

            return main.Execute(ref context);
        }
    }
}
